package org.xx.sipahi.controllers.portal;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.xx.armory.commons.ForLogging;
import org.xx.sipahi.Category;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.math.NumberUtils.toInt;
import static org.springframework.http.HttpHeaders.COOKIE;
import static org.xx.armory.commons.DebugUtils.dumpTraceBack;
import static org.xx.armory.commons.MapUtils.getBoolean;
import static org.xx.armory.commons.MapUtils.getString;
import static org.xx.armory.spring5.mvc.WebUtils.toKeyValues;

@RequestMapping("/portal")
public class PortalController {
    @ForLogging
    private final Logger logger = LoggerFactory.getLogger(PortalController.class);

    @Autowired
    private RestTemplate restTemplate;

    @Value("${app.forward-user-url:}")
    private String forwardUserUrl;

    @Value("${app.error-204-url:}")
    private String error204Url;

    @Value("${app.error-401-url:}")
    private String error401Url;

    @Value("${app.error-403-url:}")
    private String error403Url;

    @Value("${app.error-404-url:}")
    private String error404Url;

    private AuthUser forwardUser(
            HttpServletRequest request,
            String requiredAuthority
    ) {
        if (this.forwardUserUrl.isBlank()) {
            return new AuthUser("", true);
        }

        final var cookies = request.getCookies();
        final var headers = new HttpHeaders();
        if (cookies != null && cookies.length > 0) {
            headers.add(COOKIE, stream(cookies).map(cookie -> cookie.getName() + "=" + cookie.getValue()).collect(joining(";")));
        }
        final var uriVariables = new HashMap<String, Object>();
        uriVariables.put("required-authority", requiredAuthority);

        try {
            final var map = this.restTemplate.exchange(
                    this.forwardUserUrl, HttpMethod.GET, new HttpEntity<>(headers),
                    Map.class, uriVariables
            ).getBody();

            final var authUserMap = toKeyValues(map);
            return new AuthUser(
                    getString(authUserMap, "name"),
                    getBoolean(authUserMap, "hasAuthority", false)
            );
        } catch (RestClientException ex) {
            logger.warn("Cannot access forward user url: {}\n{}", this.forwardUserUrl, dumpTraceBack(ex));
            return new AuthUser("", true);
        }
    }

    protected void sendRedirect(
            HttpServletResponse response,
            String url
    )
            throws RenderException {
        try {
            response.sendRedirect(url);
        } catch (IOException ex) {
            throw new RenderException(ex);
        }
    }

    /**
     * 重定向到401响应。
     */
    protected void send401(
            HttpServletRequest request,
            HttpServletResponse response
    )
            throws RenderException {
        if (!isBlank(this.error401Url)) {
            sendRedirect(response, this.error401Url);
        } else {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }

    /**
     * 重定向到403响应。
     */
    protected void send403(
            HttpServletRequest request,
            HttpServletResponse response
    )
            throws RenderException {
        if (!isBlank(this.error403Url)) {
            sendRedirect(response, this.error403Url);
        } else {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        }
    }

    /**
     * 重定向到404响应。
     */
    protected void send404(
            HttpServletRequest request,
            HttpServletResponse response
    )
            throws RenderException {
        if (!isBlank(this.error404Url)) {
            sendRedirect(response, this.error404Url);
        } else {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        }
    }

    protected void send204(
            HttpServletRequest request,
            HttpServletResponse response
    )
            throws RenderException {
        if (!isBlank(this.error204Url)) {
            sendRedirect(response, this.error204Url);
        } else {
            response.setStatus(HttpServletResponse.SC_NO_CONTENT);
        }
    }

    protected UserCheckResult checkUser(
            Map<String, ? super Object> data,
            HttpServletRequest request,
            Category category
    ) {
        final var authUser = forwardUser(request, category.getAuthority());
        logger.debug("Auth user: {url={}, name={}, has-authority={}}", request.getContextPath(), authUser.getName(), authUser.hasAuthority());
        if (!authUser.hasAuthority()) {
            return authUser.getName().isEmpty() ? UserCheckResult.UNAUTHORIZED : UserCheckResult.FORBIDDEN;
        } else {
            data.put("user", authUser);
            return UserCheckResult.OK;
        }
    }

    protected int[] parseIdAndPage(
            String s
    ) {
        if (isBlank(s)) {
            return new int[]{0, 0};
        } else {
            final var ss = s.split("-");
            if (ss.length == 1) {
                return new int[]{toInt(ss[0], 0), 1};
            } else {
                return new int[]{toInt(ss[0], 0), toInt(ss[1], 1)};
            }
        }
    }

    protected enum UserCheckResult {
        OK,
        UNAUTHORIZED,
        FORBIDDEN
    }

    public static class AuthUser {
        private final String name;
        private final boolean hasAuthority;

        public AuthUser(
                String name,
                boolean hasAuthority
        ) {
            this.name = name;
            this.hasAuthority = hasAuthority;
        }

        public final String getName() {
            return this.name;
        }

        public final boolean hasAuthority() {
            return this.hasAuthority;
        }
    }

    protected static class RenderException
            extends RuntimeException {
        public RenderException(
                IOException cause
        ) {
            super(cause);
        }
    }
}
